import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils';
import { assignUVs, loadTexture, WallGeometry, BaseModel } from '../plugin';

export default class Zone extends BaseModel {
  constructor(options) {
    super();

    this.options = {
      bound: [],
      zones: [],
      pipes: [],
      wallHeight: 1,
      wallDuration: 3,
      ...options,
    };

    this.#init();
  }

  #init() {
    const { wallHeight } = this.options;

    const bottomPlane = this.#createBottomPlane(0x3dc2ab);
    this.object.add(bottomPlane);

    const wall = this.#createWall();
    this.object.add(wall);

    const topPlane = this.#createTopPlane();
    topPlane.position.z = wallHeight;
    this.object.add(topPlane);

    const pipe = this.#createPipe(0x19e2ff);
    pipe.name = 'pipe';
    pipe.position.z = wallHeight + 0.01;
    this.object.add(pipe);

    const boundLine = this.#createBoundLine();
    boundLine.position.z = wallHeight + 0.01;
    this.object.add(boundLine);

    // // const areaName = this.createAreaName(0xffffff);
    // // areaName.position.z = wallHeight + 0.1;
    // // this.group.add(areaName);

    const areaLine = this.#createAreaLine(0xffffff);
    areaLine.position.z = wallHeight + 0.1;
    this.object.add(areaLine);

    const areaInteractive = this.#createAreaInteractive();
    areaInteractive.position.z = wallHeight + 0.01;
    this.object.add(areaInteractive);
  }

  #createBottomPlane(color) {
    const { bound } = this.options;

    const geometryArray = bound.map((path) => {
      const points = path.map(([x, y]) => new THREE.Vector2(x, y));
      const shape = new THREE.Shape(points);
      return new THREE.ShapeGeometry(shape);
    });

    const geometry = mergeGeometries(geometryArray, true);
    const material = new THREE.MeshBasicMaterial({
      color,
      side: THREE.DoubleSide,
    });
    return new THREE.Mesh(geometry, material);
  }

  #createWall() {
    const { bound, wallHeight, wallDuration } = this.options;

    const group = new THREE.Group();

    const url = require('./assets/wall.png');
    const texture = loadTexture(url);
    texture.wrapT = THREE.RepeatWrapping;

    const material = new THREE.MeshLambertMaterial({
      color: 0x0d866a,
      map: texture,
      side: THREE.DoubleSide,
      transparent: true,
    });

    bound.forEach((path) => {
      const geometry = new WallGeometry(path, wallHeight);
      const wall = new THREE.Mesh(geometry, material);
      group.add(wall);
    });

    const tween = new TWEEN.Tween(texture.offset, this.tweenGroup).to({ y: -1 }, wallDuration * 1000).repeat(Infinity);
    this.tweenList.push(tween);

    return group;
  }

  #createTopPlane() {
    const { bound } = this.options;

    const url = require('./assets/map.png');
    const texture = loadTexture(url);

    const geometryArray = bound.map((path) => {
      const points = path.map(([x, y]) => new THREE.Vector2(x, y));
      const shape = new THREE.Shape(points);
      return new THREE.ShapeGeometry(shape);
    });

    const geometry = mergeGeometries(geometryArray, true);
    const material = new THREE.MeshBasicMaterial({
      map: texture,
    });
    assignUVs(geometry);
    return new THREE.Mesh(geometry, material);
  }

  #createPipe(color) {
    const { pipes } = this.options;

    const geometryArray = pipes.map((path) => {
      const points = path.map(([x, y]) => new THREE.Vector2(x, y));
      return new THREE.BufferGeometry().setFromPoints(points);
    });

    const geometry = mergeGeometries(geometryArray, true);
    const material = new THREE.LineBasicMaterial({
      color,
      opacity: 1,
      transparent: true,
    });
    return new THREE.LineSegments(geometry, material);
  }

  #createBoundLine() {
    const { bound } = this.options;

    const group = new THREE.Group();

    const url = require('./assets/spriteline.png');
    const texture = loadTexture(url);
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapT = THREE.RepeatWrapping;

    const tween = new TWEEN.Tween(texture.offset, this.tweenGroup).to({ x: -1 }, 3 * 1000).repeat(Infinity);
    this.tweenList.push(tween);

    const material = new THREE.MeshBasicMaterial({
      map: texture,
      side: THREE.DoubleSide,
      transparent: true,
    });

    bound.forEach((path) => {
      const curvePath = new THREE.CurvePath();

      for (let i = 0, len = path.length - 1; i < len; i++) {
        const lineCurve = new THREE.LineCurve3(new THREE.Vector3(...path[i], 0), new THREE.Vector3(...path[i + 1], 0));
        curvePath.curves.push(lineCurve);
      }

      const geometry = new THREE.TubeGeometry(curvePath, 256, 0.05, 8, false);
      const mesh = new THREE.Mesh(geometry, material);
      group.add(mesh);
    });

    return group;
  }

  #createAreaLine(color) {
    const { zones } = this.options;

    const group = new THREE.Group();

    const material = new THREE.LineBasicMaterial({
      color,
      opacity: 0.8,
      transparent: true,
    });

    zones.forEach((zone) => {
      const zoneGroup = new THREE.Group();
      group.add(zoneGroup);

      zone.polygons.forEach((path) => {
        const points = path.map(([x, y]) => new THREE.Vector2(x, y));
        const geometry = new THREE.BufferGeometry().setFromPoints(points);
        const line = new THREE.Line(geometry, material);
        zoneGroup.add(line);
      });
    });

    return group;
  }

  // createAreaName(color) {
  //   const { zones } = this.options;

  //   const group = new THREE.Group();

  //   const fontUrl = require('@/assets/fonts/Alibaba PuHuiTi 2.0 55 Regular_Regular.json');
  //   const font = new FontLoader().parse(fontUrl);

  //   const material = new THREE.MeshBasicMaterial({
  //     color,
  //   });

  //   zones.forEach((zone) => {
  //     const shapes = font.generateShapes(zone.name, 0.25);
  //     const geometry = new THREE.ShapeGeometry(shapes);

  //     geometry.computeBoundingBox();
  //     const x = geometry.boundingBox.max.x - geometry.boundingBox.min.x;
  //     geometry.translate(-x / 2, 0, 0);

  //     const mesh = new THREE.Mesh(geometry, material);
  //     mesh.position.set(...zone.center, 0);
  //     group.add(mesh);
  //   });

  //   return group;
  // }

  #createAreaInteractive() {
    const { zones } = this.options;

    const group = new THREE.Group();

    const material = new THREE.MeshBasicMaterial({
      color: 0xffffff,
      opacity: 0,
      transparent: true,
    });

    zones.forEach((zone) => {
      const zoneGroup = new THREE.Group();
      zoneGroup.name = zone.name;
      group.add(zoneGroup);

      zone.polygons.forEach((path) => {
        const points = path.map(([x, y]) => new THREE.Vector2(x, y));
        const shape = new THREE.Shape(points);
        const geometry = new THREE.ShapeGeometry(shape);
        const mesh = new THREE.Mesh(geometry, material.clone());
        zoneGroup.add(mesh);
      });
    });

    let prevGroup = null;

    const onMousemove = (intersects) => {
      const currentGroup = intersects[0]?.object.parent;

      if (currentGroup !== prevGroup) {
        if (prevGroup) {
          prevGroup.children.forEach((mesh) => {
            mesh.material.opacity = 0;
          });

          this.emit('zone:mouseleave', prevGroup.name);
        }
        if (currentGroup) {
          currentGroup.children.forEach((mesh) => {
            mesh.material.opacity = 0.25;
          });

          this.emit('zone:mouseenter', currentGroup.name);
        }
        prevGroup = currentGroup;
      }
    };

    this.listen(group, 'mousemove', onMousemove);

    return group;
  }
}
